-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(runtime-dom): Apply nested component styles when using defineCustomElements #4309
feat(runtime-dom): Apply nested component styles when using defineCustomElements #4309
Conversation
If the child components are compiled under custom elements mode, it's probably better to register them and use them as custom elements. That said, this could be a use case where the library author don't want to pre-determine the tags the elements are registered as. Unfortunately this PR wouldn't cover the case if child components are not registered using the |
Generally I'd agree, but for tightly coupled components you wouldn't be able to use provide/Inject any longer, which is quite common in these scenarios.
Indeed. Would it work and be an acceptable workaround to require component authors to register them in a second script block? <script setup>
import Child from './Child.vue'
</script>
<script>
import Child from './Child.vue'
export default {
Child
}
</script> Not exactly pretty but ... |
sorry, I accidentally close this PR....... If it default-exports an object with a component property, this PR can recursively read SFCs with the current
@yyx990803 thank you for checking this PR!
I understood that component authors have two options to creating web-components with nested vue components.
I think "export child SFCs" has smaller impact on a SFC implementation as vue components. |
The document announces usage alongside normal After reading this, I feel natural to declare components option in |
Hi, for me exporting child component in second <script> tag results in |
@pawel-marciniak I can check the cause of error if there is a reproduction code(repository). |
Is there any plan to fix this? Because for now I will need to get back to Vue 2.x. Regarding my above error - I'm using TypeScript and I have the same error when I'm using one <script setup> tag but without |
Hey, I'm facing the same problem now. Is there any plan to fix this? |
I've added the |
@@ -341,4 +342,28 @@ export class VueElement extends BaseClass { | |||
}) | |||
} | |||
} | |||
|
|||
private _getStylesRecursively( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only addresses styles for locally registered non-async components. Async or global components would still have no styles.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider a web component to be an isolated and self-contained component in the shadow DOM, which is fully loaded and registered as a module.
I would be concerned if code was dynamically reloaded in the shadow DOM, so I don't see the need for async components.
Also, would this inject styles into the shadow DOM from child components imported within the As an example, if I define and export a component with |
You could do a workaround. It's situational and doesn't cover everything, but it might suit your usecase. import { ref, onMounted, getCurrentInstance } from 'vue'
export const useStyles = (removeStyleTag: boolean = true) => {
const shadowRoot = ref<ShadowRoot>()
onMounted(() => {
const instance = getCurrentInstance()
if (!instance) return
shadowRoot.value = instance.vnode?.el?.getRootNode()
if (!shadowRoot.value) return
// @ts-expect-error
const styles = instance.type.styles.join('')
// Use __hmrId as UUID for replacing styles, instead of adding new ones every time.
// @ts-expect-error
const __hmrId = instance.type.__hmrId
if (instance.isCE) {
// Don't remove styletag in dev, because runtime would try to remove non existing dom element on update.
if (import.meta.env.DEV || !removeStyleTag) return
// Not the best to let the runtime first add a styletag and than remove it, but we don't want to touch the vue runtime code.
shadowRoot.value
.querySelectorAll('style')
.forEach((style) => style.remove())
}
adoptStyles(shadowRoot.value, styles, __hmrId)
})
}
/**
* Whether the current browser supports `adoptedStyleSheets`.
*/
export const supportsAdoptingStyleSheets =
window.ShadowRoot &&
'adoptedStyleSheets' in Document.prototype &&
'replaceSync' in CSSStyleSheet.prototype
/**
* Add constructed Stylesheet or style tag to Shadowroot of VueCE.
* @param renderRoot The shadowroot of the vueCE..
* @param styles The styles of the Element.
* @param __hmrId hmr id of vite used as an UUID.
*/
export const adoptStyles = (
renderRoot: ShadowRoot,
styles: string,
__hmrId: string
) => {
if (supportsAdoptingStyleSheets) {
const sheets = renderRoot.adoptedStyleSheets
const oldSheet = sheets.find((sheet) => sheet.__hmrId === __hmrId)
// Check if this StyleSheet exists already. Replace content if it does. Otherwise construct a new CSSStyleSheet.
if (oldSheet) {
oldSheet.replaceSync(styles)
} else {
const styleSheet: CSSStyleSheet = new CSSStyleSheet()
styleSheet.__hmrId = __hmrId
styleSheet.replaceSync(styles)
renderRoot.adoptedStyleSheets = [
...renderRoot.adoptedStyleSheets,
styleSheet,
]
}
} else {
const existingStyleElements = renderRoot.querySelectorAll('style')
const oldStyleElement = Array.from(existingStyleElements).find(
(sheet) => sheet.title === __hmrId
)
// Check if this Style Element exists already. Replace content if it does. Otherwise construct a new HTMLStyleElement.
if (oldStyleElement) {
oldStyleElement.innerHTML = styles
} else {
const styleElement = document.createElement('style')
styleElement.title = __hmrId
styleElement.innerHTML = styles
renderRoot.appendChild(styleElement)
}
}
} <template>
<div class="abc">ABC</div>
</template>
<script setup lang="ts">
import { useStyles } from '../composables/useStyles'
useStyles()
</script>
<style>
.abc {
background: red;
}
</style>
<style>
.abc {
color: purple;
}
</style> <template>
<Abc />
</template>
<script setup lang="ts">
import Abc from './Abc.ce.vue'
</script> |
Using children this way means then you have to deal with DOM style props (slightly different syntax, arrays as string trap) and grabbing values from an ugly attribute array inside of custom events. This is just as unsatisfying as defining all styles in the root element. I may be wrong, but the developer experience for custom elements is currently not nearly as good as for regular Vue applications (although still a great project). |
Is there any update on this? Not including the styles of child components makes it impossible to use component-based UI libraries in a custom element. Is that a bad practice to begin with, am I missing something? |
Are there a workaround for this? Some indication of where this is going would be nice. A simple "not in the near future", "or not possible in v3" is fine. A long running open issue like this is a bit difficult when taking decitions. |
Hello! Any news on this PR? |
Bump |
I think the whole custom element api should get a look at. There are a lot of painpoints.
|
Motivation
defineCustomElements
is supporting SFC having style tag, but nested SFC's styles are not applied.Even if SFC tools import nested SFCs in custom element mode and each SFCs have styles as property,
_applyStyles
is received root SFC's style only and nested SFCs' styles are ignored.I think it's commonly that a SFC has some smaller SFCs.
Changes
apiCustomElement.ts
_getStylesRecursively
components
property.styles
property.-
_getStylesRecursively
read component recursively using eachcomponents
properties and readstyles
property if a component has._getStylesRecursively
returns the array._applyStyles
inconnectedCallback
(_resolveDef
).customElement.spec.ts
test('should attach styles recursively to shadow dom')
)